{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "1l8bWGmIJuQa"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Authors.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "CPSnXS88KFEo"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "89xNCIO5hiCj"
      },
      "source": [
        "# 분산 전략을 사용한 모델 저장 및 불러오기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9Ejs4QVxIdAm"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://www.tensorflow.org/tutorials/distribute/save_and_load\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" />TensorFlow.org에서 보기</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ko/tutorials/distribute/save_and_load.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />구글 코랩(Colab)에서 실행하기</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ko/tutorials/distribute/save_and_load.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />깃허브(GitHub)소스 보기</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ko/tutorials/distribute/save_and_load.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\" />노트북 다운로드 하기</a>\n",
        "  </td>\n",
        "\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "FITHltVKQ4eZ"
      },
      "source": [
        "Note: 이 문서는 텐서플로 커뮤니티에서 번역했습니다. 커뮤니티 번역 활동의 특성상 정확한 번역과 최신 내용을 반영하기 위해 노력함에도 불구하고 [공식 영문 문서](https://www.tensorflow.org/?hl=en)의 내용과 일치하지 않을 수 있습니다. 이 번역에 개선할 부분이 있다면 [tensorflow/docs-l10n](https://github.com/tensorflow/docs-l10n) 깃허브 저장소로 풀 리퀘스트를 보내주시기 바랍니다. 문서 번역이나 리뷰에 참여하려면 [docs-ko@tensorflow.org](https://groups.google.com/a/tensorflow.org/forum/#!forum/docs-ko)로 메일을 보내주시기 바랍니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "A0lG6qgThxAS"
      },
      "source": [
        "## 개요\n",
        "\n",
        "훈련 도중 모델을 저장하고 불러오는 것은 흔히 일어나는 일입니다. 케라스 모델을 저장하고 불러오기 위한 API에는 high-level API와 low-level API, 두 가지가 있습니다. 이 튜토리얼은 `tf.distribute.Strategy`를 사용할 때 어떻게 SavedModel APIs를 사용할 수 있는지 보여줍니다. SavedModel과 직렬화에 관한 일반적인 내용을 학습하려면, [saved model guide](../../guide/saved_model.ipynb)와 [Keras model serialization guide](../../guide/keras/save_and_serialize.ipynb)를 읽어보는 것을 권장합니다. 간단한 예로 시작해보겠습니다:"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "FITHltVKQ4eZ"
      },
      "source": [
        "필요한 패키지 가져오기:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "RWG5HchAiOrZ"
      },
      "outputs": [],
      "source": [
        "!pip install tf-nightly\n",
        "import tensorflow_datasets as tfds\n",
        "\n",
        "import tensorflow as tf\n",
        "tfds.disable_progress_bar()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "qqapWj98ptNV"
      },
      "source": [
        "`tf.distribute.Strategy`를 사용하는 모델과 데이터 준비하기:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "yrYiAf_ziRyw"
      },
      "outputs": [],
      "source": [
        "mirrored_strategy = tf.distribute.MirroredStrategy()\n",
        "\n",
        "def get_data():\n",
        "  datasets, ds_info = tfds.load(name='mnist', with_info=True, as_supervised=True)\n",
        "  mnist_train, mnist_test = datasets['train'], datasets['test']\n",
        "\n",
        "  BUFFER_SIZE = 10000\n",
        "\n",
        "  BATCH_SIZE_PER_REPLICA = 64\n",
        "  BATCH_SIZE = BATCH_SIZE_PER_REPLICA * mirrored_strategy.num_replicas_in_sync\n",
        "\n",
        "  def scale(image, label):\n",
        "    image = tf.cast(image, tf.float32)\n",
        "    image /= 255\n",
        "\n",
        "    return image, label\n",
        "\n",
        "  train_dataset = mnist_train.map(scale).cache().shuffle(BUFFER_SIZE).batch(BATCH_SIZE)\n",
        "  eval_dataset = mnist_test.map(scale).batch(BATCH_SIZE)\n",
        "\n",
        "  return train_dataset, eval_dataset\n",
        "\n",
        "def get_model():\n",
        "  with mirrored_strategy.scope():\n",
        "    model = tf.keras.Sequential([\n",
        "        tf.keras.layers.Conv2D(32, 3, activation='relu', input_shape=(28, 28, 1)),\n",
        "        tf.keras.layers.MaxPooling2D(),\n",
        "        tf.keras.layers.Flatten(),\n",
        "        tf.keras.layers.Dense(64, activation='relu'),\n",
        "        tf.keras.layers.Dense(10)\n",
        "    ])\n",
        "\n",
        "    model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n",
        "                  optimizer=tf.keras.optimizers.Adam(),\n",
        "                  metrics=['accuracy'])\n",
        "    return model"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "qmU4Y3feS9Na"
      },
      "source": [
        "모델 훈련시키기: "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zmGurbJmS_vN"
      },
      "outputs": [],
      "source": [
        "model = get_model()\n",
        "train_dataset, eval_dataset = get_data()\n",
        "model.fit(train_dataset, epochs=2)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "L01wjgvRizHS"
      },
      "source": [
        "## 모델 저장하고 불러오기\n",
        "이제 사용할 모델을 가지고 있으므로 API를 이용해 모델을 저장하고 불러오는 방법에 대해 살펴봅시다. \n",
        "두 가지 API를 사용 할 수 있습니다:\n",
        "\n",
        "*   고수준 케라스 `model.save`와 `tf.keras.models.load_model`\n",
        "*   저수준 케라스 `tf.saved_model.save`와 `tf.saved_model.load`\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "FX_IF2F1tvFs"
      },
      "source": [
        "### 케라스 API"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "O8xfceg4Z3H_"
      },
      "source": [
        "케라스 API들을 이용해 모델을 저장하고 불러오는 예를 소개합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "LYOStjV5knTQ"
      },
      "outputs": [],
      "source": [
        "keras_model_path = \"/tmp/keras_save\"\n",
        "model.save(keras_model_path)  # save()는 전략 범위를 벗어나 호출되어야 합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "yvQIdQp3zNMp"
      },
      "source": [
        "`tf.distribute.Strategy`없이 모델 복원시키기:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "WrXAAVtrzRgv"
      },
      "outputs": [],
      "source": [
        "restored_keras_model = tf.keras.models.load_model(keras_model_path)\n",
        "restored_keras_model.fit(train_dataset, epochs=2)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "gYAnskzorda-"
      },
      "source": [
        "모델을 복원시킨 후에는 `compile()`이 이미 저장되기 전에 컴파일 되기 때문에, `compile()`을 다시 호출하지 않고도 모델 훈련을 계속 할 수 있습니다. 그 모델은 텐서플로 표준 `SavedModel`의 프로토 타입에 저장됩니다. 더 많은 정보를 원한다면, [the guide to `saved_model` format](../../guide/saved_model.ipynb)를 참고하세요.\n",
        "\n",
        "`tf.distribute.strategy`의 범위를 벗어나서 `model.save()` 방법을 호출하는 것은 중요합니다. 범위 안에서 호출하는 것은 지원하지 않습니다. \n",
        "\n",
        "이제 모델을 불러와서 `tf.distribute.Strategy`를 사용해 훈련시킵니다:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "wROPrJaAqBQz"
      },
      "outputs": [],
      "source": [
        "another_strategy = tf.distribute.OneDeviceStrategy(\"/cpu:0\")\n",
        "with another_strategy.scope():\n",
        "  restored_keras_model_ds = tf.keras.models.load_model(keras_model_path)\n",
        "  restored_keras_model_ds.fit(train_dataset, epochs=2)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PdiiPmL5tQk5"
      },
      "source": [
        "위에서 볼 수 있듯이, 불러오기는 `tf.distribute.Strategy`에서 예상한대로 작동합니다. 여기서 사용된 전략은 이전에 사용된 전략과 같지 않아도 됩니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "3CrXIbmFt0f6"
      },
      "source": [
        "### `tf.saved_model` 형 API"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HtGzPp6et4Em"
      },
      "source": [
        "이제 저수준 API에 대해서 살펴봅시다. 모델을 저장하는 것은 케라스 API와 비슷합니다:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4y6T31APuCqK"
      },
      "outputs": [],
      "source": [
        "model = get_model()  # 새 모델 얻기\n",
        "saved_model_path = \"/tmp/tf_save\"\n",
        "tf.saved_model.save(model, saved_model_path)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "q1QNRYcwuRll"
      },
      "source": [
        "`tf.saved_model.load()`로 불러올 수 있습니다. 그러나 저수준 단계의 API이기 때문에 (따라서 더 넓은 사용범위를 갖습니다), 케라스 모델을 반환하지 않습니다. 대신, 추론하기 위해 사용될 수 있는 기능들을 포함한 객체를 반환합니다. 예를 들어:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "aaEKqBSPwAuM"
      },
      "outputs": [],
      "source": [
        "DEFAULT_FUNCTION_KEY = \"serving_default\"\n",
        "loaded = tf.saved_model.load(saved_model_path)\n",
        "inference_func = loaded.signatures[DEFAULT_FUNCTION_KEY]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "x65l7AaHUZCA"
      },
      "source": [
        "불러와진 객체는 각각 키와 관련된 채, 여러 기능을 포함할 수 있습니다. `\"serving_default\"`는 저장된 케라스 모델이 있는 추론 기능을 위한 기본 키입니다. 이 기능을 이용하여 추론합니다: "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5Ore5q8-UjW1"
      },
      "outputs": [],
      "source": [
        "predict_dataset = eval_dataset.map(lambda image, label: image)\n",
        "for batch in predict_dataset.take(1):\n",
        "  print(inference_func(batch))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "osB1LY8WwUJZ"
      },
      "source": [
        "또한 분산방식으로 불러오고 추론할 수 있습니다:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "iDYvu12zYTmT"
      },
      "outputs": [],
      "source": [
        "another_strategy = tf.distribute.MirroredStrategy()\n",
        "with another_strategy.scope():\n",
        "  loaded = tf.saved_model.load(saved_model_path)\n",
        "  inference_func = loaded.signatures[DEFAULT_FUNCTION_KEY]\n",
        "\n",
        "  dist_predict_dataset = another_strategy.experimental_distribute_dataset(\n",
        "      predict_dataset)\n",
        "\n",
        "  # 분산방식으로 기능 호출하기\n",
        "  for batch in dist_predict_dataset:\n",
        "    another_strategy.run(inference_func,args=(batch,))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hWGSukoyw3fF"
      },
      "source": [
        "복원된 기능을 호출하는 것은 단지 저장된 모델로의 정방향 패쓰입니다(예상하기에). 만약 계속해서 불러온 기능을 훈련시키고 싶다면 어떻게 하실건가요? 불러온 기능을 더 큰 모델에 내장시킬 건가요? 일반적인 방법은 이 불러온 객체를 케라스 층에 싸서(wrap) 달성하는 것입니다. 다행히도, [TF Hub](https://www.tensorflow.org/hub)는 이 목적을 위해 [hub.KerasLayer](https://github.com/tensorflow/hub/blob/master/tensorflow_hub/keras_layer.py)을 갖고 있으며, 다음과 같습니다. "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "clfk3hQoyKu6"
      },
      "outputs": [],
      "source": [
        "import tensorflow_hub as hub\n",
        "\n",
        "def build_model(loaded):\n",
        "  x = tf.keras.layers.Input(shape=(28, 28, 1), name='input_x')\n",
        "  # KerasLayer로 감싸기\n",
        "  keras_layer = hub.KerasLayer(loaded, trainable=True)(x)\n",
        "  model = tf.keras.Model(x, keras_layer)\n",
        "  return model\n",
        "\n",
        "another_strategy = tf.distribute.MirroredStrategy()\n",
        "with another_strategy.scope():\n",
        "  loaded = tf.saved_model.load(saved_model_path)\n",
        "  model = build_model(loaded)\n",
        "\n",
        "  model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n",
        "                optimizer=tf.keras.optimizers.Adam(),\n",
        "                metrics=['accuracy'])\n",
        "  model.fit(train_dataset, epochs=2)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Oe1z_OtSJlu2"
      },
      "source": [
        "볼 수 있듯이, `hub.KerasLayer`은 `tf.saved_model.load()`로부터 불려온 결과를 또 다른 모델을 만드는데 사용될 수 있는 케라스 층으로 포장(wrap)합니다. 이것은 학습에 매우 유용합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "KFDOZpK5Wa3W"
      },
      "source": [
        "### 어떤 API를 사용해야 할까요?"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "GC6GQ9HDLxD6"
      },
      "source": [
        "저장에 관해서, 케라스 모델을 사용하는 경우, 케라스의 `model.save()` API를 사용하는 것을 권장합니다. 저장하려는 모델이 케라스 모델이 아닌 경우, 더 낮은 단계의 API를 선택해야 합니다.\n",
        "\n",
        "모델을 불러옴에 있어서, 어떤 API를 사용하느냐는 로딩 API에서 얻고자 하는 내용에 따라 결정됩니다. 케라스 모델을 가져올 수 없으면(또는 가져오고 싶지 않다면), `tf.saved_model.load()`를 사용합니다. 그 외의 경우에는, `tf.keras.models.load_model()`을 사용합니다. 케라스 모델을 저장한 경우에만 케라스 모델을 반환 받을 수 있다는 점을 유의하세요. \n",
        "\n",
        "API들을 목적에 따라 혼합하고 짜 맞추는 것이 가능합니다. 케라스 모델을 `model.save`와 함께 저장할 수 있고, 저수준 API인, `tf.saved_model.load`로 케라스가 아닌 모델을 불러올 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Ktwg2GwnXE8v"
      },
      "outputs": [],
      "source": [
        "model = get_model()\n",
        "\n",
        "# 케라스의 save() API를 사용하여 모델 저장하기\n",
        "model.save(keras_model_path) \n",
        "\n",
        "another_strategy = tf.distribute.MirroredStrategy()\n",
        "# 저수준 API를 사용하여 모델 불러오기\n",
        "with another_strategy.scope():\n",
        "  loaded = tf.saved_model.load(keras_model_path)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hJTWOnC9iuA3"
      },
      "source": [
        "### 주의사항"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Tzog2ti7YYgy"
      },
      "source": [
        "특별한 경우는 잘 정의되지 않은 입력을 갖는 케라스 모델을 갖고 있는 경우입니다. 예를 들어, 순차 모델은 입력 형태(`Sequential([Dense(3), ...]`) 없이 만들 수 있습니다. 하위 분류된 모델들 또한 초기화 후에 잘 정의된 입력을 갖고 있지 않습니다. 이 경우 모델을 저장하고 불러올 시 저수준 API를 사용해야 하며, 그렇지 않으면 오류가 발생할 수 있습니다.\n",
        "\n",
        "모델이 잘 정의된 입력을 갖는지 확인하려면, `model.inputs` 이 `None`인지 확인합니다. `None`이 아니라면 잘 정의된 입력입니다. 입력 형태들은 모델이 `.fit`, `.evaluate`, `.predict`에서 쓰이거나 모델을 호출 (`model(inputs)`) 할 때 자동으로 정의됩니다. \n",
        "\n",
        "예시를 살펴봅시다:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gurSIbDFjOBc"
      },
      "outputs": [],
      "source": [
        "class SubclassedModel(tf.keras.Model):\n",
        "\n",
        "  output_name = 'output_layer'\n",
        "\n",
        "  def __init__(self):\n",
        "    super(SubclassedModel, self).__init__()\n",
        "    self._dense_layer = tf.keras.layers.Dense(\n",
        "        5, dtype=tf.dtypes.float32, name=self.output_name)\n",
        "\n",
        "  def call(self, inputs):\n",
        "    return self._dense_layer(inputs)\n",
        "\n",
        "my_model = SubclassedModel()\n",
        "# my_model.save(keras_model_path)  # 오류! \n",
        "tf.saved_model.save(my_model, saved_model_path)"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "save_and_load.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
